// import com.alibaba.druid.sql.ast.SQLExpr;
// import com.alibaba.druid.sql.ast.SQLName;
// import com.alibaba.druid.sql.ast.expr.SQLListExpr;
// import com.alibaba.druid.sql.ast.statement.*;
// import com.alibaba.druid.sql.dialect.hive.ast.HiveInputOutputFormat;
// import com.alibaba.druid.sql.dialect.hive.stmt->HiveCreateTableStatement;
// import com.alibaba.druid.sql.parser.*;
// import com.alibaba.druid.util.FnvHash;

#include "HiveCreateTableParser.h"
#include "HiveExprParser.h"
#include "../../../../utils/FnvHash.h"
#include "../stmt/HiveCreateTableStatement.h"
#include "../ast/HiveInputOutputFormat.h"
#include "../../../parser/SQLSelectParser.h"
#include "HiveSelectParser.h"

HiveCreateTableParser::HiveCreateTableParser(SQLExprParser_ptr exprParser)
    : SQLCreateTableParser(exprParser)
{
}

HiveCreateTableParser::HiveCreateTableParser(Lexer_ptr lexer)
    : SQLCreateTableParser(HiveExprParser_ptr(new HiveExprParser(lexer)))
{
}

SQLCreateTableStatement_ptr HiveCreateTableParser::parseCreateTable(BOOL_ptr acceptCreate)
{
  HiveCreateTableStatement_ptr stmt = newCreateStatement();

  if (acceptCreate)
  {
    if (lexer->hasComment() && lexer->isKeepComments())
    {
      stmt->addBeforeComment(lexer->readAndResetComments());
    }

    accept(Token::CREATE);
  }

  if (lexer->identifierEquals(FnvHash::Constants::EXTERNAL))
  {
    lexer->nextToken();
    stmt->setExternal(BOOL::TRUE);
  }

  if (lexer->identifierEquals(FnvHash::Constants::TEMPORARY))
  {
    lexer->nextToken();
    stmt->setType(SQLCreateTableStatement::Type::TEMPORARY);
  }

  accept(Token::TABLE);

  if (*lexer->token() == Token::IF || lexer->identifierEquals(FnvHash::Constants::IF))
  {
    lexer->nextToken();
    accept(Token::NOT);
    accept(Token::EXISTS);

    stmt->setIfNotExiists(BOOL::TRUE);
  }

  stmt->setName(this->exprParser->name());

  if (*lexer->token() == Token::LPAREN)
  {
    lexer->nextToken();

    for (;;)
    {
      Token_ptr token = lexer->token();
      if (*token == Token::IDENTIFIER //
          || *token == Token::LITERAL_ALIAS)
      {
        SQLColumnDefinition_ptr column = this->exprParser->parseColumn();
        stmt->getTableElementList()->push_back(column);
      }
      else if (*token == Token::PRIMARY   //
               || *token == Token::UNIQUE //
               || *token == Token::CHECK  //
               || *token == Token::CONSTRAINT || *token == Token::FOREIGN)
      {
        SQLConstraint_ptr constraint = this->exprParser->parseConstaint();
        constraint->setParent(stmt);
        stmt->getTableElementList()->push_back(std::dynamic_pointer_cast<SQLTableElement>(constraint));
      }
      else if (*token == Token::TABLESPACE)
      {
        throw new ParserException(make_string_ptr("TODO " + encode_util::UnicodeToUTF8(lexer->info())));
      }
      else
      {
        SQLColumnDefinition_ptr column = this->exprParser->parseColumn();
        stmt->getTableElementList()->push_back(column);
      }

      if (*lexer->token() == Token::COMMA)
      {
        lexer->nextToken();

        if (*lexer->token() == Token::RPAREN)
        { // compatible for sql server
          break;
        }
        continue;
      }

      break;
    }

    accept(Token::RPAREN);
  }
  else if (*lexer->token() == Token::LIKE)
  {
    parseLike(stmt);
  }

  if (lexer->identifierEquals(FnvHash::Constants::ENGINE))
  {
    // skip engine=xxx
    lexer->nextToken();
    accept(Token::EQ);
    lexer->nextToken();
  }

  if (lexer->identifierEquals(FnvHash::Constants::CHARSET))
  {
    // skip charset = xxx
    lexer->nextToken();
    accept(Token::EQ);
    lexer->nextToken();
  }

  if (lexer->identifierEquals(FnvHash::Constants::USING) || *lexer->token() == Token::USING)
  {
    lexer->nextToken();
    SQLExpr_ptr using_ = this->exprParser->expr();
    stmt->setUsing(using_);
  }

  if (lexer->identifierEquals(FnvHash::Constants::OPTIONS))
  {
    lexer->nextToken();
    accept(Token::LPAREN);
    parseAssignItems(stmt->getTableOptions(), stmt, BOOL::FALSE);
    accept(Token::RPAREN);
  }

  if (*lexer->token() == Token::COMMENT)
  {
    lexer->nextToken();
    SQLExpr_ptr comment = this->exprParser->expr();
    stmt->setComment(comment);
  }

  if (lexer->identifierEquals(FnvHash::Constants::MAPPED))
  {
    lexer->nextToken();
    accept(Token::BY);
    this->exprParser->parseAssignItem(stmt->getMappedBy(), stmt);
  }

  if (*lexer->token() == Token::PARTITIONED)
  {
    lexer->nextToken();
    accept(Token::BY);
    accept(Token::LPAREN);

    for (;;)
    {
      if (*lexer->token() != Token::IDENTIFIER)
      {
        throw new ParserException(make_string_ptr("expect identifier. " + encode_util::UnicodeToUTF8(lexer->info())));
      }

      SQLColumnDefinition_ptr column = this->exprParser->parseColumn();
      stmt->addPartitionColumn(column);

      if (lexer->isKeepComments() && lexer->hasComment())
      {
        string_list_ptr tmp = std::make_shared<std::list<string_ptr>>();
        for (auto it : *lexer->readAndResetComments())
        {
          tmp->push_back(make_string_ptr(encode_util::UnicodeToUTF8(*it)));
        }
        column->addAfterComment(tmp);
      }

      if (*lexer->token() != Token::COMMA)
      {
        break;
      }
      else
      {
        lexer->nextToken();
        if (lexer->isKeepComments() && lexer->hasComment())
        {
          string_list_ptr tmp = std::make_shared<std::list<string_ptr>>();
          for (auto it : *lexer->readAndResetComments())
          {
            tmp->push_back(make_string_ptr(encode_util::UnicodeToUTF8(*it)));
          }
          column->addAfterComment(tmp);
        }
      }
    }

    accept(Token::RPAREN);
  }

  if (lexer->identifierEquals(FnvHash::Constants::CLUSTERED))
  {
    lexer->nextToken();
    accept(Token::BY);
    accept(Token::LPAREN);
    for (;;)
    {
      SQLSelectOrderByItem_ptr item = this->exprParser->parseSelectOrderByItem();
      stmt->addClusteredByItem(item);
      if (*lexer->token() == Token::COMMA)
      {
        lexer->nextToken();
        continue;
      }
      break;
    }
    accept(Token::RPAREN);
  }

  if (lexer->identifierEquals(FnvHash::Constants::SKEWED))
  {
    lexer->nextToken();
    accept(Token::BY);
    accept(Token::LPAREN);
    this->exprParser->exprList(stmt->getSkewedBy(), stmt);
    accept(Token::RPAREN);
    accept(Token::ON);
    accept(Token::LPAREN);
    for (;;)
    {
      if (*lexer->token() == Token::LPAREN)
      {
        SQLListExpr_ptr list = SQLListExpr_ptr(new SQLListExpr());
        lexer->nextToken();
        this->exprParser->exprList(list->getItems(), list);
        accept(Token::RPAREN);
        stmt->addSkewedByOn(list);
      }
      else
      {
        SQLExpr_ptr expr = this->exprParser->expr();
        stmt->addSkewedByOn(expr);
      }
      if (*lexer->token() == Token::COMMA)
      {
        lexer->nextToken();
        continue;
      }
      break;
    }
    accept(Token::RPAREN);
  }

  if (lexer->identifierEquals(FnvHash::Constants::SORTED))
  {
    parseSortedBy(stmt);
  }

  if (*lexer->token() == Token::ROW || lexer->identifierEquals(FnvHash::Constants::ROW))
  {
    parseRowFormat(stmt);
  }

  if (lexer->identifierEquals(FnvHash::Constants::SORTED))
  {
    parseSortedBy(stmt);
  }

  if (stmt->getClusteredBy()->size() > 0 || stmt->getSortedBy()->size() > 0)
  {
    accept(Token::INTO);
    if (*lexer->token() == Token::LITERAL_INT)
    {
      stmt->setBuckets(lexer->integerValue());
      lexer->nextToken();
    }
    else
    {
      throw new ParserException(make_string_ptr("into buckets must be integer. " + encode_util::UnicodeToUTF8(lexer->info())));
    }
    acceptIdentifier("BUCKETS");
  }

  if (*lexer->token() == Token::ROW || lexer->identifierEquals(FnvHash::Constants::ROW))
  {
    parseRowFormat(stmt);
  }

  if (lexer->identifierEquals(FnvHash::Constants::STORED))
  {
    lexer->nextToken();
    accept(Token::AS);

    if (lexer->identifierEquals(FnvHash::Constants::INPUTFORMAT))
    {
      HiveInputOutputFormat_ptr format = HiveInputOutputFormat_ptr(new HiveInputOutputFormat());
      lexer->nextToken();
      format->setInput(this->exprParser->primary());

      if (lexer->identifierEquals(FnvHash::Constants::OUTPUTFORMAT))
      {
        lexer->nextToken();
        format->setOutput(this->exprParser->primary());
      }
      stmt->setStoredAs(format);
    }
    else
    {
      SQLName_ptr name = this->exprParser->name();
      stmt->setStoredAs(name);
    }
  }

  if (lexer->identifierEquals(FnvHash::Constants::LOCATION))
  {
    lexer->nextToken();
    SQLExpr_ptr location = this->exprParser->primary();
    stmt->setLocation(location);
  }

  if (*lexer->token() == Token::LIKE)
  {
    parseLike(stmt);
  }

  if (lexer->identifierEquals(FnvHash::Constants::TBLPROPERTIES))
  {
    parseTblProperties(stmt);
  }

  if (lexer->identifierEquals(FnvHash::Constants::META))
  {
    lexer->nextToken();
    acceptIdentifier("LIFECYCLE");
    stmt->setMetaLifeCycle(this->exprParser->primary());
  }

  if (*lexer->token() == Token::AS)
  {
    lexer->nextToken();
    SQLSelect_ptr select = this->createSQLSelectParser()->select();
    stmt->setSelect(select);
  }

  if (*lexer->token() == Token::LIKE)
  {
    lexer->nextToken();
    Lexer_SavePoint_ptr mark = lexer->mark();
    if (*lexer->token() == Token::SELECT)
    {
      stmt->setLikeQuery(BOOL::TRUE);
      SQLSelect_ptr select = this->createSQLSelectParser()->select();
      stmt->setSelect(select);
    }
    else
    {
      lexer->reset(mark);

      if (lexer->identifierEquals(FnvHash::Constants::MAPPING))
      {
        SQLExpr_ptr like = this->exprParser->primary();
        stmt->setLike(SQLExprTableSource_ptr(new SQLExprTableSource(like)));
      }
      else
      {
        SQLName_ptr name = this->exprParser->name();
        stmt->setLike(name);
      }
    }
  }

  if (*lexer->token() == Token::COMMENT)
  {
    lexer->nextToken();
    SQLExpr_ptr comment = this->exprParser->expr();
    stmt->setComment(comment);
  }

  if (lexer->identifierEquals(FnvHash::Constants::USING) || *lexer->token() == Token::USING)
  {
    lexer->nextToken();
    SQLExpr_ptr using_ = this->exprParser->expr();
    stmt->setUsing(using_);
  }

  if (lexer->identifierEquals(FnvHash::Constants::TBLPROPERTIES))
  {
    lexer->nextToken();
    accept(Token::LPAREN);
    parseAssignItems(stmt->getTblProperties(), stmt, BOOL::FALSE);
    accept(Token::RPAREN);
  }

  return stmt;
}

void HiveCreateTableParser::parseTblProperties(HiveCreateTableStatement_ptr stmt)
{
  lexer->nextToken();
  accept(Token::LPAREN);

  for (;;)
  {
    string_ptr name = make_string_ptr(encode_util::UnicodeToUTF8(*lexer->stringVal()));
    lexer->nextToken();
    if (*lexer->token() == Token::DOT)
    {
      lexer->nextToken();
      name->append("." + encode_util::UnicodeToUTF8(*lexer->stringVal()));
      lexer->nextToken();
    }

    accept(Token::EQ);
    SQLExpr_ptr value = this->exprParser->primary();
    stmt->addTblProperty(name, value);
    if (*lexer->token() == Token::COMMA)
    {
      lexer->nextToken();
      if (*lexer->token() == Token::RPAREN)
      {
        break;
      }
      continue;
    }
    break;
  }

  accept(Token::RPAREN);
}

void HiveCreateTableParser::parseLike(HiveCreateTableStatement_ptr stmt)
{
  lexer->nextToken();

  if (lexer->identifierEquals(FnvHash::Constants::MAPPING))
  {
    SQLExpr_ptr like = this->exprParser->primary();
    stmt->setLike(SQLExprTableSource_ptr(new SQLExprTableSource(like)));
  }
  else if (*lexer->token() == Token::SELECT || *lexer->token() == Token::LPAREN)
  {
    SQLSelect_ptr select = this->createSQLSelectParser()->select();
    stmt->setLikeQuery(BOOL::TRUE);
    stmt->setSelect(select);
  }
  else
  {
    SQLName_ptr name = this->exprParser->name();
    stmt->setLike(name);
  }
}

void HiveCreateTableParser::parseSortedBy(HiveCreateTableStatement_ptr stmt)
{
  lexer->nextToken();
  accept(Token::BY);
  accept(Token::LPAREN);
  for (;;)
  {
    SQLSelectOrderByItem_ptr item = this->exprParser->parseSelectOrderByItem();
    stmt->addSortedByItem(item);
    if (*lexer->token() == Token::COMMA)
    {
      lexer->nextToken();
      continue;
    }
    break;
  }
  accept(Token::RPAREN);
}

void HiveCreateTableParser::parseRowFormat(HiveCreateTableStatement_ptr stmt)
{
  SQLExternalRecordFormat_ptr format = this->getExprParser()->parseRowFormat();
  stmt->setRowFormat(format);

  if (*lexer->token() == Token::WITH)
  {
    lexer->nextToken();
    acceptIdentifier("SERDEPROPERTIES");

    accept(Token::LPAREN);

    for (;;)
    {
      std::string name = encode_util::UnicodeToUTF8(*lexer->stringVal());
      lexer->nextToken();
      accept(Token::EQ);
      SQLExpr_ptr value = this->exprParser->primary();
      stmt->getSerdeProperties()->insert(std::make_pair(name, value));
      if (*lexer->token() == Token::COMMA)
      {
        lexer->nextToken();
        continue;
      }
      break;
    }

    accept(Token::RPAREN);
  }
}

// @Override
HiveExprParser_ptr HiveCreateTableParser::getExprParser()
{
  return std::dynamic_pointer_cast<HiveExprParser>(exprParser);
}

HiveCreateTableStatement_ptr HiveCreateTableParser::newCreateStatement()
{
  return HiveCreateTableStatement_ptr(new HiveCreateTableStatement());
}

SQLSelectParser_ptr HiveCreateTableParser::createSQLSelectParser()
{
  return SQLSelectParser_ptr(new HiveSelectParser(this->exprParser, selectListCache));
}
